// @tags: [
//     requires_getmore,
//     # This test relies on listIncex command returning specific batch-sized responses.
//     assumes_no_implicit_cursor_exhaustion,
// ]

// Basic functional tests for the listIndexes command.

import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
import {IndexUtils} from "jstests/libs/index_utils.js";

let coll = db.list_indexes1;
let cursor;
let res;
let specs;

//
// Test basic command output.
//

coll.drop();
assert.commandWorked(coll.getDB().createCollection(coll.getName()));
res = coll.runCommand("listIndexes");
assert.commandWorked(res);
assert.eq("object", typeof res.cursor);
assert.eq(0, res.cursor.id);
assert.eq(coll.getFullName(), res.cursor.ns);
if (!FixtureHelpers.isSharded(coll)) {
    assert.eq(1, res.cursor.firstBatch.length);
} else {
    assert.gte(res.cursor.firstBatch.length, 1);
}
assert.eq("_id_", res.cursor.firstBatch[0].name);

//
// Test basic usage with DBCommandCursor.
//

let getListIndexesCursor = function (coll, options, subsequentBatchSize) {
    return new DBCommandCursor(coll.getDB(), coll.runCommand("listIndexes", options), subsequentBatchSize);
};

let cursorGetIndexSpecs = function (cursor) {
    return cursor.toArray();
};

let cursorGetIndexKeys = function (cursor) {
    return cursorGetIndexSpecs(cursor).map(function (spec) {
        return spec.key;
    });
};

coll.drop();
assert.commandWorked(coll.getDB().createCollection(coll.getName()));
IndexUtils.assertIndexesMatch(coll, [{_id: 1}], cursorGetIndexKeys(getListIndexesCursor(coll)));

//
// Test that the index metadata object is returned correctly.
//

coll.drop();
assert.commandWorked(coll.getDB().createCollection(coll.getName()));
assert.commandWorked(coll.createIndex({a: 1}, {sparse: true}));
const specIndexes = cursorGetIndexSpecs(getListIndexesCursor(coll));
IndexUtils.assertIndexesMatch(
    coll,
    [{_id: 1}, {a: 1}],
    specIndexes.map((spec) => spec.key),
);

const idSpec = specIndexes.find((spec) => spec.name === "_id_");
assert.neq(undefined, idSpec);
assert.eq(undefined, idSpec.sparse);
assert.eq(true, specIndexes.find((spec) => spec.name === "a_1").sparse);

//
// Test that the command does not accept invalid values for the collection.
//

assert.commandFailed(coll.getDB().runCommand({listIndexes: ""}));
assert.commandFailed(coll.getDB().runCommand({listIndexes: 1}));
assert.commandFailed(coll.getDB().runCommand({listIndexes: {}}));
assert.commandFailed(coll.getDB().runCommand({listIndexes: []}));

//
// Test basic usage of "cursor.batchSize" option.
//

coll.drop();
assert.commandWorked(coll.getDB().createCollection(coll.getName()));
assert.commandWorked(coll.createIndex({a: 1}, {sparse: true}));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: 2}});
assert.eq(2, cursor.objsLeftInBatch());
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: 1}});
assert.eq(1, cursor.objsLeftInBatch());
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: 0}});
assert.eq(0, cursor.objsLeftInBatch());
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: NumberInt(2)}});
assert.eq(2, cursor.objsLeftInBatch());
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: NumberLong(2)}});
assert.eq(2, cursor.objsLeftInBatch());
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: Math.pow(2, 62)}});
assert.gte(cursor.objsLeftInBatch(), 2);
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

// Ensure that the server accepts an empty object for "cursor".  This is equivalent to not
// specifying "cursor" at all.
//
// We do not test for objsLeftInBatch() here, since the default batch size for this command is
// not specified.
cursor = getListIndexesCursor(coll, {cursor: {}});
IndexUtils.assertIndexesMatch(coll, [{_id: 1}, {a: 1}], cursorGetIndexKeys(cursor));

//
// Test more than 2 batches of results.
//

coll.drop();
assert.commandWorked(coll.getDB().createCollection(coll.getName()));
assert.commandWorked(coll.createIndex({a: 1}, {sparse: true}));
assert.commandWorked(coll.createIndex({b: 1}, {sparse: true}));
assert.commandWorked(coll.createIndex({c: 1}, {sparse: true}));

cursor = getListIndexesCursor(coll, {cursor: {batchSize: 0}}, 2);
assert.eq(0, cursor.objsLeftInBatch());
assert(cursor.hasNext());
assert.eq(2, cursor.objsLeftInBatch());

cursor.next();
assert(cursor.hasNext());
assert.eq(1, cursor.objsLeftInBatch());

cursor.next();
assert(cursor.hasNext());
assert.eq(2, cursor.objsLeftInBatch());

cursor.next();
assert(cursor.hasNext());
assert.eq(1, cursor.objsLeftInBatch());

cursor.next();
if (!FixtureHelpers.isSharded(coll) && !TestData.implicitWildcardIndexesEnabled) {
    assert(!cursor.hasNext());
}

//
// Test killCursors against a listCollections cursor.
//

coll.drop();
assert.commandWorked(coll.getDB().createCollection(coll.getName()));
assert.commandWorked(coll.createIndex({a: 1}, {sparse: true}));
assert.commandWorked(coll.createIndex({b: 1}, {sparse: true}));
assert.commandWorked(coll.createIndex({c: 1}, {sparse: true}));

res = coll.runCommand("listIndexes", {cursor: {batchSize: 0}});
cursor = new DBCommandCursor(coll.getDB(), res, 2);
cursor.close();
cursor = new DBCommandCursor(coll.getDB(), res, 2);
assert.throws(function () {
    cursor.hasNext();
});
